package cn.doourbest.learnspring.inteceptor;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
				RowBounds.class, ResultHandler.class }),
		@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class PaginationStatementHandlerInterceptor implements Interceptor {

	private final static Logger logger = LoggerFactory.getLogger(PaginationStatementHandlerInterceptor.class);
	private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

	Dialect dialect = null;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

		// MappedStatement mappedStatement =
		// (MappedStatement)ReflectHelper.getFieldValue(statementHandler,
		// "mappedStatement");

		ParameterHandler parameterHandler = statementHandler.getParameterHandler();
		BoundSql boundSql = statementHandler.getBoundSql();

		long startTime = System.currentTimeMillis();

		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
				DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);

		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");

		RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");

		Configuration configuration = mappedStatement.getConfiguration();

		String sql = getSql(configuration, boundSql);

		String params = getParams(configuration, boundSql);

		String oldSql = boundSql.getSql().replaceAll("[\\s]+", " ");

		String id = SqlThreadPool.getId();
		if (id == null || id.equals("")) {
			id = "未获取到用户id";
		}
		// 没有分页参数
		if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
			//打印sql日志
			printSqlLog(invocation, mappedStatement, id, oldSql, sql, params, startTime);
			return invocation.proceed();
		}

		// Configuration configuration = (Configuration)
		// metaStatementHandler.getValue("delegate.configuration");
		String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		// 获取总记录数
		Page<?> page = (Page<?>) rowBounds;
		int offset = page.getOffset();
		int limit = page.getLimit();
		if (PageConstant.NOT_PAGING < offset && PageConstant.NOT_PAGING < limit) {
			String countSql = dialect.getCountString(originalSql);
			Connection connection = (Connection) invocation.getArgs()[0];
			int total = getTotal(parameterHandler, connection, countSql);
			page.setTotalCount(total);
			// 设置物理分页语句
			metaStatementHandler.setValue("delegate.boundSql.sql",
					dialect.getLimitString(originalSql, page.getOffset(), page.getLimit()));
		}

		// 屏蔽mybatis原有分页
		metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
		metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
		if (logger.isDebugEnabled()) {
//          logger.debug("分页SQL : " + boundSql.getSql());
			printSqlLog(invocation, mappedStatement, id, oldSql, sql, params, startTime);
		}

		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		}
		return target;
	}

	@Override
	public void setProperties(Properties properties) {
		String dialectClass = properties.getProperty("dialectClass");
		try {
			dialect = (Dialect) Class.forName(dialectClass).newInstance();
		} catch (Exception e) {
			throw new RuntimeException("cannot create dialect instance by dialectClass:" + dialectClass, e);
		}
	}

	private void printSqlLog(Invocation invocation, MappedStatement mappedStatement, String id, String oldSql,
                             String sql, String params, long startTime) throws InvocationTargetException, IllegalAccessException {

		if (logger.isDebugEnabled()) {
//          logger.debug("分页SQL : " + boundSql.getSql());
			logger.info("用户ID:" + id + "," + "打印时间:" + sdf.format(new Date()) + "," + "调用方法:" + mappedStatement.getId()
					+ "," + "原始sql:" + oldSql + "," + "完整SQL:" + sql + "," + "参数:" + params + "," + "查询结果:"
					+ invocation.proceed().toString() + "," + "执行时间:" + (System.currentTimeMillis() - startTime)
					+ "毫秒");
		}
		SqlThreadPool.removeId();
	}

	/**
	 * 获取总计录
	 * 
	 * @param parameterHandler
	 * @param connection
	 * @param countSql
	 * @return
	 * @throws Exception
	 */
	private int getTotal(ParameterHandler parameterHandler, Connection connection, String countSql) throws Exception {
		// MetaObject metaStatementHandler =
		// MetaObject.forObject(parameterHandler);
		// Object parameterObject =
		// metaStatementHandler.getValue("parameterObject");
		PreparedStatement prepareStatement = connection.prepareStatement(countSql);
		parameterHandler.setParameters(prepareStatement);
		ResultSet rs = prepareStatement.executeQuery();
		int count = 0;
		if (rs.next()) {
			count = rs.getInt(1);
		}
		rs.close();
		prepareStatement.close();
		return count;
	}

	private String getSql(Configuration configuration, BoundSql boundSql) {
		String sql = boundSql.getSql();
		try {
			sql = sql.replaceAll("[\\s]+", " ");
			Object parameterObject = boundSql.getParameterObject();
			List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
			if (parameterObject == null || parameterMappings.size() == 0) {
				return sql;
			}
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
				sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
			} else {
				MetaObject metaObject = configuration.newMetaObject(parameterObject);
				for (ParameterMapping parameterMapping : parameterMappings) {
					String propertyName = parameterMapping.getProperty();
					if (metaObject.hasGetter(propertyName)) {
						Object obj = metaObject.getValue(propertyName);
						sql = sql.replaceFirst("\\?", getParameterValue(obj));
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						Object obj = boundSql.getAdditionalParameter(propertyName);
						sql = sql.replaceFirst("\\?", getParameterValue(obj));
					}
				}
			}
			
		}catch(Exception e){
			return sql;
		}
		return sql;
	}

	private String getParameterValue(Object obj) {
		String value = null;
		if (obj instanceof String) {
			value = "'" + obj.toString() + "'";
		} else if (obj instanceof Date) {
			DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
			value = "'" + formatter.format(obj) + "'";
		} else {
			if (obj != null) {
				value = obj.toString();
			} else {
				value = "";
			}
		}

		return java.util.regex.Matcher.quoteReplacement(value);
	}

	private String getParams(Configuration configuration, BoundSql boundSql) {

		Object parameterObject = boundSql.getParameterObject();
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

		if (parameterObject == null || parameterMappings.size() == 0) {
			return null;
		} else {
			String params = "";
			MetaObject metaObject = configuration.newMetaObject(parameterObject);
			for (ParameterMapping parameterMapping : parameterMappings) {
				String propertyName = parameterMapping.getProperty();
				if (metaObject.hasGetter(propertyName)) {
					Object obj = metaObject.getValue(propertyName);
					params += propertyName + "=" + getParameterValue(obj) + ",";
				} else if (boundSql.hasAdditionalParameter(propertyName)) {
					Object obj = boundSql.getAdditionalParameter(propertyName);
					params += propertyName + "=" + getParameterValue(obj) + ",";
				}

			}

			return params;
		}

	}

	public static void main(String[] args) {
		String sql = "select count(*) from t_app_info where application_name = ? and application_type =?";
		String ss = "~!@#$%";
		sql = sql.replaceFirst("\\?", ss);

		System.out.println(sql);

	}

}